package by.dvrudenko.avtobaza.dataaccess.impl;

import java.sql.Timestamp;
import java.util.List;
import java.util.Random;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.SingularAttribute;

import org.apache.commons.lang3.Validate;

import by.dvrudenko.avtobaza.dataaccess.AbstractDao;
import by.dvrudenko.avtobaza.dataaccess.en.Restriction;

public abstract class AbstractDaoImpl<ID, Entity> implements
		AbstractDao<ID, Entity> {
	
    protected final Random random = new Random();

	private EntityManager em;
	public EntityManager getEm() {
		return em;
	}

	public void setEm(EntityManager em) {
		this.em = em;
	}

	private final Class<Entity> entityClass;

	protected AbstractDaoImpl(final Class<Entity> entityClass) {
		Validate.notNull(entityClass, "entityClass could not be a null");
		this.entityClass = entityClass;
	}

	@Override
	public Entity getById(ID id) {
		return em.find(getEntityClass(), id);
	}

	@Override
	public Entity insert(final Entity entity) {
		em.persist(entity);
		return entity;
	}

	@Override
	public Entity update(Entity entity) {
		entity = em.merge(entity);
		em.flush();
		return entity;
	}

	@Override
	public void delete(final ID key) {
		em.remove(em.find(getEntityClass(), key));
	}

	@Override
	public void delete(List<ID> ids) {
		em.createQuery(String.format("delete from %s e where e.id in (:ids)",
				entityClass.getSimpleName())).setParameter("ids", ids).executeUpdate();
	}

	@Override
	public void deleteAll() {
		final Query q1 = em.createQuery("delete from "+ getEntityClass().getSimpleName());q1.executeUpdate();
		em.flush();
	}

	@Override
	public List<Entity> getAll() {
		final CriteriaQuery<Entity> query = em.getCriteriaBuilder()
				.createQuery(getEntityClass());
		query.from(getEntityClass());
		final List<Entity> lst = em.createQuery(query).getResultList();
		return lst;
	}
	
	@Override
	public List<Entity> getAllByFieldRestriction(final SingularAttribute<? super Entity, ?> attribute, final Object value) {
		Validate.notNull(value, "Search attributes can't be empty. Attribute: " + attribute.getName());
		final CriteriaBuilder builder = em.getCriteriaBuilder();
		final CriteriaQuery<Entity> criteria = builder.createQuery(getEntityClass());
		final Root<Entity> root = criteria.from(getEntityClass());
		criteria.select(root).distinct(true);
		criteria.where(builder.equal(root.get(attribute), value));
		return em.createQuery(criteria).getResultList();
	}
	
	@Override
	public List<Entity> getAllByFieldGreaterOrLessThan(final SingularAttribute<? super Entity, Integer> attribute, 
			final Integer value,Restriction restriction) {
		Validate.notNull(value, "Search attributes can't be empty. Attribute: " + attribute.getName());
 		final CriteriaBuilder builder = em.getCriteriaBuilder();
		final CriteriaQuery<Entity> criteria = builder.createQuery(getEntityClass());
		final Root<Entity> root = criteria.from(getEntityClass());
		criteria.select(root).distinct(true);	
		
		if (restriction.equals(Restriction.less)){
			criteria.select(root).where(builder.lessThan(root.get(attribute),value));
		}else if (restriction.equals(Restriction.greater)){
			criteria.select(root).where(builder.greaterThan(root.get(attribute),value));
		}else {
			throw new IllegalArgumentException (restriction + " - condition must be equlas less or greater");
		}
		
		return em.createQuery(criteria).getResultList();
	}
	
	@Override
	public List<Entity> getAllByFieldByDate(final SingularAttribute<? super Entity, Timestamp> attribute, final Timestamp value,
			Restriction restriction) {
		Validate.notNull(value, "Search attributes can't be empty. Attribute: " + attribute.getName());
 		final CriteriaBuilder builder = em.getCriteriaBuilder();
		final CriteriaQuery<Entity> criteria = builder.createQuery(getEntityClass());
		final Root<Entity> root = criteria.from(getEntityClass());
		criteria.select(root).distinct(true);	
		
		if (restriction.equals(Restriction.less)){
			criteria.select(root).where(builder.lessThan(root.get(attribute),value));
		}else if (restriction.equals(Restriction.greater)){
			criteria.select(root).where(builder.greaterThan(root.get(attribute),value));
		}else{
			throw new IllegalArgumentException (restriction + " - condition must be equlas less or greater");
		}
		
		return em.createQuery(criteria).getResultList();
	}
	
	@Override	
	public Entity randomEntity() {
		// JPQL
	      Query countQuery = em.createQuery("select count(id) from "+getEntityClass().getName());
	      long count = (Long)countQuery.getSingleResult();

	      int number = random.nextInt((int)count);

	      Query selectQuery = em.createQuery("from "+getEntityClass().getName());
	      selectQuery.setFirstResult(number);
	      selectQuery.setMaxResults(1);
	      return (Entity) selectQuery.getSingleResult();
	 }
	
	
	/*
	 * Аннотация @PersistenceContext предназаначена для автоматического
	 * связывания менеджера сущностей с бином
	 * инициализация em
	 */
	@PersistenceContext
	protected void setEntityManager(final EntityManager em) {
		this.em = em;
	}

	private Class<Entity> getEntityClass() {
		return entityClass;
	}
	
	public int randInt(int min, int max) {
	    // NOTE: Usually this should be a field rather than a method
	    // variable so that it is not re-seeded every call.
	    Random rand = new Random();
	    // nextInt is normally exclusive of the top value,
	    // so add 1 to make it inclusive
	    int randomNum = rand.nextInt((max - min) + 1) + min;
	    return randomNum;
	}
}
